package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
	"text/template"

	"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
	"github.com/gogo/protobuf/vanity/command"

	"gitee.com/nggs/tools/pbplugin"
)

var msgRegexp = regexp.MustCompile(`\s*@msg(?:\s*=\s*(\d+))?\s*`)
var enableZeroRegexp = regexp.MustCompile(`\s*@enable_0(?:\s*=\s*(\w+))?\s*`)

var (
	fieldDescriptorProtoTypes = map[descriptor.FieldDescriptorProto_Type]string{
		descriptor.FieldDescriptorProto_TYPE_DOUBLE:   "double",
		descriptor.FieldDescriptorProto_TYPE_FLOAT:    "float",
		descriptor.FieldDescriptorProto_TYPE_INT64:    "int64",
		descriptor.FieldDescriptorProto_TYPE_UINT64:   "uint64",
		descriptor.FieldDescriptorProto_TYPE_INT32:    "int32",
		descriptor.FieldDescriptorProto_TYPE_FIXED64:  "fixed64",
		descriptor.FieldDescriptorProto_TYPE_FIXED32:  "fixed32",
		descriptor.FieldDescriptorProto_TYPE_BOOL:     "bool",
		descriptor.FieldDescriptorProto_TYPE_STRING:   "string",
		descriptor.FieldDescriptorProto_TYPE_GROUP:    "group",
		descriptor.FieldDescriptorProto_TYPE_MESSAGE:  "message",
		descriptor.FieldDescriptorProto_TYPE_BYTES:    "bytes",
		descriptor.FieldDescriptorProto_TYPE_UINT32:   "uint32",
		descriptor.FieldDescriptorProto_TYPE_ENUM:     "enum",
		descriptor.FieldDescriptorProto_TYPE_SFIXED32: "sfixed32",
		descriptor.FieldDescriptorProto_TYPE_SFIXED64: "sfixed64",
		descriptor.FieldDescriptorProto_TYPE_SINT32:   "sint32",
		descriptor.FieldDescriptorProto_TYPE_SINT64:   "sint64",
	}
)

type messageField struct {
	d   *Descriptor
	fdp *descriptor.FieldDescriptorProto

	Name                string
	JsonName            string
	CamelCaseName       string
	Comment             string
	DescriptorProtoType string
	DefaultValue        string

	IsOptional bool
	IsRequired bool
	IsRepeated bool
	IsScalar   bool

	GoType       string
	GoTypeToName string
}

type message struct {
	g *Generator

	ID            string
	Name          string
	CamelCaseName string
	Type          string
	Comment       string
	Fields        []*messageField
}

type enumValue struct {
	ed   *EnumDescriptor
	evdp *descriptor.EnumValueDescriptorProto

	Name     string
	TypeName string
	Value    int32
	Comment  string
}

type enum struct {
	g *Generator

	Name          string
	CamelCaseName string
	Comment       string
	Values        []*enumValue
	Enable0       string
}

type protoFile struct {
	Name        string
	PackageName string
	Messages    []*message
	Enums       []*enum
}

func parse(g *Generator, fd *FileDescriptor) *protoFile {
	pf := &protoFile{
		Name: strings.TrimSuffix(*fd.Name, ".proto"),
	}

	pf.PackageName = fd.PackageName()

	for _, md := range fd.Messages() {
		m := &message{
			g:       g,
			Name:    pbplugin.CamelCaseSlice(md.TypeName()),
			Comment: g.GetComments(md.Path()),
		}
		m.CamelCaseName = pbplugin.CamelCase(m.Name)

		if matches := msgRegexp.FindStringSubmatch(m.Comment); len(matches) > 1 {
			m.ID = matches[1]
			//m.Comment = strings.Trim(m.Comment, matches[0])
			//m.Comment = strings.Trim(m.Comment, matches[1])
		}

		for j, fdp := range md.GetField() {
			f := &messageField{
				d:                   md,
				fdp:                 fdp,
				Name:                g.GetFieldName(md, fdp),
				JsonName:            fdp.GetJsonName(),
				Comment:             g.GetComments(fmt.Sprintf("%s,%d,%d", md.Path(), 2, j)),
				DescriptorProtoType: fieldDescriptorProtoTypes[*fdp.Type],
				IsOptional:          pbplugin.IsOptional(fdp),
				IsRequired:          pbplugin.IsRequired(fdp),
				IsRepeated:          pbplugin.IsRepeated(fdp),
				IsScalar:            pbplugin.IsScalar(fdp),
			}
			f.CamelCaseName = pbplugin.CamelCase(f.Name)
			f.GoType, _ = m.g.GoType(f.d, f.fdp)
			f.GoTypeToName = pbplugin.GoTypeToName(f.GoType)
			if fdp.DefaultValue != nil {
				f.DefaultValue = *fdp.DefaultValue
			}

			m.Fields = append(m.Fields, f)
		}

		pf.Messages = append(pf.Messages, m)
	}

	for i, ed := range fd.Enums() {
		name := ed.GetName()
		//if name == "MSG" {
		//	// 跳过叫MSG的枚举
		//	continue
		//}

		if ed.Parent() != nil {
			name = ed.Prefix() + name
		}

		e := &enum{
			g:             g,
			Name:          name,
			CamelCaseName: pbplugin.CamelCase(name),
			Comment:       g.Comments(fmt.Sprintf("5,%d", i)),
		}

		if matches := enableZeroRegexp.FindStringSubmatch(e.Comment); len(matches) > 0 {
			e.Enable0 = "true"
		}

		for j, edp := range ed.GetValue() {
			v := &enumValue{
				ed:       ed,
				evdp:     edp,
				Name:     *edp.Name,
				TypeName: e.CamelCaseName,
				Value:    *edp.Number,
				Comment:  g.Comments(fmt.Sprintf("5,%d,2,%d", i, j)),
			}

			e.Values = append(e.Values, v)
		}

		pf.Enums = append(pf.Enums, e)
	}

	return pf
}

type myPlugin struct {
	*Generator
	//imports pbplugin.PluginImports
}

func newPlugin() *myPlugin {
	p := &myPlugin{}
	return p
}

func (myPlugin) Name() string {
	return "pbex-ts"
}

func (p *myPlugin) Init(g *Generator) {
	p.Generator = g
	//p.imports = pbplugin.NewPluginImports(g)
}

func (p *myPlugin) Generate(fd *FileDescriptor) {
	var code bytes.Buffer
	err := gTpl.Execute(&code, parse(p.Generator, fd))
	if err != nil {
		panic(err)
	}
	s := code.String()
	p.P(s)
}

func (p *myPlugin) GenerateImports(fd *FileDescriptor) {
	//p.PrintImport("", "sync")
	//p.PrintImport("protocol", "gitee.com/nggs/network/protocol/protobuf/v1")
	//
	//p.P("var _ *sync.Pool")
	//p.P("var _ = protocol.PH")
}

var (
	gTpl *template.Template
)

func main() {
	req := command.Read()

	reqParams := map[string]string{}
	if req.GetParameter() != "" {
		reqParams = pbplugin.ParseRequestParameterString(req.GetParameter())
	}

	if templateFilePath, ok := reqParams["tpl"]; ok {
		tpl, err := ioutil.ReadFile(templateFilePath)
		if err != nil {
			panic(err)
		}
		gTpl, err = template.New("pbex").Funcs(gTemplateFuncMap).Parse(string(tpl))
		if err != nil {
			panic(err)
		}
	} else {
		var err error
		gTpl, err = template.New("pbex").Funcs(gTemplateFuncMap).Parse(defaultTemplate)
		if err != nil {
			panic(err)
		}
	}

	p := newPlugin()
	var fileNameSuffix string
	if extend, ok := reqParams["ex"]; ok {
		fileNameSuffix = ".pbex." + extend
	} else {
		fileNameSuffix = ".pbex.ts"
	}
	res := Generate(req, p, fileNameSuffix)
	command.Write(res)
}
